package logger

import (
	"errors"
	"gitee.com/sunny-zhangqiang/zcache_new/comm"
	"os"
	"path/filepath"
	"sync"
	"time"
)

type ScrollingMode int

const (
	ScrollingNone = ScrollingMode(iota)
	ScrollingMinMode
	ScrollingHourMode
	ScrollingDayMode
)

type FileOutput struct {
	sync.RWMutex
	logFileName   string
	realFileName  string
	scrollingMode ScrollingMode
	file          *os.File
	close         chan struct{}
	closed        bool
}

func timeFileName(logFile string, scrollingMode ScrollingMode) (string, error) {
	timeLogFile, err := filepath.Abs(logFile)
	if err != nil {
		return "", err
	}

	if scrollingMode == ScrollingMinMode {
		return timeLogFile + "." + time.Now().Format("200601021504"), nil
	} else if scrollingMode == ScrollingHourMode {
		return timeLogFile + "." + time.Now().Format("2006010215"), nil
	} else if scrollingMode == ScrollingDayMode {
		return timeLogFile + "." + time.Now().Format("20060102"), nil
	}

	return timeLogFile, nil
}

func (fo *FileOutput) Output(level Level, s string) {
	fo.RLock()
	if !fo.closed {
		fo.file.Write(comm.String2Bytes(s))
	}
	fo.RUnlock()
}

func (fo *FileOutput) Flush() {
	// nothing
}

func (fo *FileOutput) Close() {
	fo.close <- struct{}{}

	fo.Lock()
	defer fo.Unlock()
	fo.closed = true
	fo.file.Close()
}

func (fo *FileOutput) tryScrollingLogFile() {
	realFileName, err := timeFileName(fo.logFileName, fo.scrollingMode)
	if err != nil {
		return
	}

	if realFileName == fo.realFileName {
		return
	}

	file, err := os.OpenFile(realFileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return
	}

	os.Remove(fo.logFileName)
	os.Symlink(realFileName, fo.logFileName)

	fo.Lock()
	if fo.file != nil {
		fo.file.Close()
	}
	fo.file = file
	fo.realFileName = realFileName
	fo.Unlock()
}

func (fo *FileOutput) scrollingLoop() {
	ticker := time.NewTicker(time.Second * 60)
	defer ticker.Stop()
	for {
		select {
		case <-fo.close:
			return
		case <-ticker.C:
			fo.tryScrollingLogFile()
		}
	}
}

func NewFileOutput(logFileName string, model ScrollingMode) (*FileOutput, error) {
	logFileName, err := filepath.Abs(logFileName)
	if err != nil {
		return nil, err
	}

	if logFileName == "/" {
		return nil, errors.New("not allowed logFileName")
	}

	realFileName, err := timeFileName(logFileName, model)
	if err != nil {
		return nil, err
	}

	_ = os.MkdirAll(filepath.Dir(realFileName), 0755)
	file, err := os.OpenFile(realFileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return nil, err
	}

	fo := &FileOutput{
		logFileName:   logFileName,
		realFileName:  realFileName,
		scrollingMode: model,
		file:          file,
		close:         make(chan struct{}),
	}

	if model != ScrollingNone {
		os.Remove(logFileName)
		os.Symlink(realFileName, logFileName)
	}

	go fo.scrollingLoop()

	return fo, nil
}
